Тестирование изменений, связанных с внедрением улучшенной рекомендательной системы для пользователей интернет-магазина — оценить результаты A/B теста

Описание проекта:
Необходимо провести оценку результатов A/B-теста, чтобы оценить измененияй, связанные с внедрением улучшенной рекомендательной системы для пользователей интернет-магазина. Мы располагаем следующими датасетами:

  • календарь маркетинговых событий на 2020 год;
  • пользователи, зарегистрировавшиеся с 7 до 21 декабря 2020 года;
  • действия новых пользователей в период с 7 декабря 2020 по 4 января 2021 года;
  • таблица участников тестов.

Цель:
оценить корректность проведения результатов A/B-теста и проанализировать его результаты.

Задача: удостовериться, что нет пересечений с конкурирующим тестом и нет пользователей, участвующих в двух группах теста одновременно; проверить равномерность распределения пользователей по тестовым группам и правильность их формирования.

Описание данных:
Данные находятся в файлах final_ab_events.csv, ab_project_marketing_events.csv, final_ab_new_users.csv, final_ab_participants.csv

Выводы:
В данном проекте построена воронка продаж, исследован путь пользователей до покупки. Проанализированы результаты A/B-теста внедрения улучшенной рекомендательной системы. Проведено сравнение двух групп. Выявлено, что новая рекомендательная система значительно не повлияет на поведение пользователей.

План выполнения проекта:

Загрузить данные, изучить общую информацию

Исследовать данные:
- Исследовать необходимость преобразования типов (преобразовать даты, обрезать даты).
- Описать природу пропущенных значений (выяснить почему возникли).
- Исследовать наличие дубликатов.
- Оценить корректность проведения теста.
- Проверить соответствие данных требованиям технического задания. Проверить корректность всех пунктов технического задания.
- Проверить время проведения теста. Убедиться, что оно не совпадает с маркетинговыми и другими активностями.
- Проверить аудиторию теста. Удостовериться, что нет пересечений с конкурирующим тестом и нет пользователей, участвующих в двух группах теста одновременно. Проверить равномерность распределения по тестовым группам и правильность их формирования.

Провести исследовательский анализ данных:
- Количество событий на пользователя одинаково распределены в выборках?
- Как число событий в выборках распределено по дням?
- Как меняется конверсия в воронке в выборках на разных этапах?
- Какие особенности данных нужно учесть, прежде чем приступать к A/B-тестированию?

Оценить результаты A/B-тестирования
- Что можно сказать про результаты A/В-тестирования?
- Проверьте статистическую разницу долей z-критерием.

Описать выводы по этапу исследовательского анализа данных и по проведённой оценке результатов A/B-тестирования. Сделать общее заключение о корректности проведения теста.

1. Загрузить данные, изучить общую информацию

In [1]:
import pandas as pd
import datetime as dt
from datetime import datetime, timedelta
import numpy as np
import matplotlib.pyplot as plt
import scipy.stats as st
import seaborn as sns

import math as mth

from statsmodels.stats.proportion import proportions_ztest

import matplotlib as mpl

from plotly import graph_objects as go

from scipy.stats import shapiro

import plotly.express as px
import re
C:\__MACOSX\ANACONDA\lib\site-packages\statsmodels\tools\_testing.py:19: FutureWarning: pandas.util.testing is deprecated. Use the functions in the public API at pandas.testing instead.
  import pandas.util.testing as tm
In [2]:
final_ab_events = pd.read_csv("C:\\Users\\Айза\\Desktop\\portfolio\\final_ab_events.csv")
final_ab_events
Out[2]:
user_id event_dt event_name details
0 E1BDDCE0DAFA2679 2020-12-07 20:22:03 purchase 99.99
1 7B6452F081F49504 2020-12-07 09:22:53 purchase 9.99
2 9CD9F34546DF254C 2020-12-07 12:59:29 purchase 4.99
3 96F27A054B191457 2020-12-07 04:02:40 purchase 4.99
4 1FD7660FDF94CA1F 2020-12-07 10:15:09 purchase 4.99
... ... ... ... ...
440312 245E85F65C358E08 2020-12-30 19:35:55 login NaN
440313 9385A108F5A0A7A7 2020-12-30 10:54:15 login NaN
440314 DB650B7559AC6EAC 2020-12-30 10:59:09 login NaN
440315 F80C9BDDEA02E53C 2020-12-30 09:53:39 login NaN
440316 7AEC61159B672CC5 2020-12-30 11:36:13 login NaN

440317 rows × 4 columns

In [3]:
ab_project_marketing_events = pd.read_csv("C:\\Users\\Айза\\Desktop\\portfolio\\ab_project_marketing_events.csv")
ab_project_marketing_events
Out[3]:
name regions start_dt finish_dt
0 Christmas&New Year Promo EU, N.America 2020-12-25 2021-01-03
1 St. Valentine's Day Giveaway EU, CIS, APAC, N.America 2020-02-14 2020-02-16
2 St. Patric's Day Promo EU, N.America 2020-03-17 2020-03-19
3 Easter Promo EU, CIS, APAC, N.America 2020-04-12 2020-04-19
4 4th of July Promo N.America 2020-07-04 2020-07-11
5 Black Friday Ads Campaign EU, CIS, APAC, N.America 2020-11-26 2020-12-01
6 Chinese New Year Promo APAC 2020-01-25 2020-02-07
7 Labor day (May 1st) Ads Campaign EU, CIS, APAC 2020-05-01 2020-05-03
8 International Women's Day Promo EU, CIS, APAC 2020-03-08 2020-03-10
9 Victory Day CIS (May 9th) Event CIS 2020-05-09 2020-05-11
10 CIS New Year Gift Lottery CIS 2020-12-30 2021-01-07
11 Dragon Boat Festival Giveaway APAC 2020-06-25 2020-07-01
12 Single's Day Gift Promo APAC 2020-11-11 2020-11-12
13 Chinese Moon Festival APAC 2020-10-01 2020-10-07
In [4]:
final_ab_new_users = pd.read_csv("C:\\Users\\Айза\\Desktop\\portfolio\\final_ab_new_users.csv")
final_ab_new_users
Out[4]:
user_id first_date region device
0 D72A72121175D8BE 2020-12-07 EU PC
1 F1C668619DFE6E65 2020-12-07 N.America Android
2 2E1BF1D4C37EA01F 2020-12-07 EU PC
3 50734A22C0C63768 2020-12-07 EU iPhone
4 E1BDDCE0DAFA2679 2020-12-07 N.America iPhone
... ... ... ... ...
61728 1DB53B933257165D 2020-12-20 EU Android
61729 538643EB4527ED03 2020-12-20 EU Mac
61730 7ADEE837D5D8CBBD 2020-12-20 EU PC
61731 1C7D23927835213F 2020-12-20 EU iPhone
61732 8F04273BB2860229 2020-12-20 EU Android

61733 rows × 4 columns

In [5]:
final_ab_participants = pd.read_csv("C:\\Users\\Айза\\Desktop\\portfolio\\final_ab_participants.csv")
final_ab_participants
Out[5]:
user_id group ab_test
0 D1ABA3E2887B6A73 A recommender_system_test
1 A7A3664BD6242119 A recommender_system_test
2 DABC14FDDFADD29E A recommender_system_test
3 04988C5DF189632E A recommender_system_test
4 482F14783456D21B B recommender_system_test
... ... ... ...
18263 1D302F8688B91781 B interface_eu_test
18264 3DE51B726983B657 A interface_eu_test
18265 F501F79D332BE86C A interface_eu_test
18266 63FBE257B05F2245 A interface_eu_test
18267 79F9ABFB029CF724 B interface_eu_test

18268 rows × 3 columns

In [6]:
final_ab_events.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 440317 entries, 0 to 440316
Data columns (total 4 columns):
 #   Column      Non-Null Count   Dtype  
---  ------      --------------   -----  
 0   user_id     440317 non-null  object 
 1   event_dt    440317 non-null  object 
 2   event_name  440317 non-null  object 
 3   details     62740 non-null   float64
dtypes: float64(1), object(3)
memory usage: 13.4+ MB
In [7]:
ab_project_marketing_events.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 14 entries, 0 to 13
Data columns (total 4 columns):
 #   Column     Non-Null Count  Dtype 
---  ------     --------------  ----- 
 0   name       14 non-null     object
 1   regions    14 non-null     object
 2   start_dt   14 non-null     object
 3   finish_dt  14 non-null     object
dtypes: object(4)
memory usage: 576.0+ bytes
In [8]:
final_ab_new_users.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 61733 entries, 0 to 61732
Data columns (total 4 columns):
 #   Column      Non-Null Count  Dtype 
---  ------      --------------  ----- 
 0   user_id     61733 non-null  object
 1   first_date  61733 non-null  object
 2   region      61733 non-null  object
 3   device      61733 non-null  object
dtypes: object(4)
memory usage: 1.9+ MB
In [9]:
final_ab_participants.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 18268 entries, 0 to 18267
Data columns (total 3 columns):
 #   Column   Non-Null Count  Dtype 
---  ------   --------------  ----- 
 0   user_id  18268 non-null  object
 1   group    18268 non-null  object
 2   ab_test  18268 non-null  object
dtypes: object(3)
memory usage: 428.3+ KB
In [10]:
final_ab_events.describe()
Out[10]:
details
count 62740.000000
mean 23.877631
std 72.180465
min 4.990000
25% 4.990000
50% 4.990000
75% 9.990000
max 499.990000
In [11]:
ab_project_marketing_events.describe()
Out[11]:
name regions start_dt finish_dt
count 14 14 14 14
unique 14 6 14 14
top Christmas&New Year Promo APAC 2020-12-25 2021-01-03
freq 1 4 1 1
In [12]:
final_ab_new_users.describe()
Out[12]:
user_id first_date region device
count 61733 61733 61733 61733
unique 61733 17 4 4
top D72A72121175D8BE 2020-12-21 EU Android
freq 1 6290 46270 27520
In [13]:
final_ab_participants.describe()
Out[13]:
user_id group ab_test
count 18268 18268 18268
unique 16666 2 2
top 0FDFDA0B2DEC2D91 A interface_eu_test
freq 2 9655 11567

В датасете final_ab_events (действия новых пользователей в период с 7 декабря 2020 по 4 января 2021 года) необходимо привести в соответствие тип данных столбца event_dt. В датасете данные о 440317 новых пользователях. В данных столбца details 377577 пропущенных значений. Необходимо исследовать к какому типу пропуски относятся и зависят ли от данных.

В датасете ab_project_marketing_events (календарь маркетинговых событий на 2020 год) необходимо привести в соответствие типы данных столбцов start_dt, finish_dt. Всего данные о 13 маркетинговых событиях.

В датасете final_ab_new_users (пользователи, зарегистрировавшиеся с 7 до 21 декабря 2020 года) необходимо привести в соответствие тип данных столбца first_date. В датасете данные о 61733 пользователях.

В датасете final_ab_participants (таблица участников тестов) информация о 18268 участниках теста.

2. Исследовать данные:

Исследовать необходимость преобразования типов

Описать природу пропущенных значений

In [14]:
final_ab_events.isna().sum()
Out[14]:
user_id            0
event_dt           0
event_name         0
details       377577
dtype: int64
In [15]:
ab_project_marketing_events.isna().sum()
Out[15]:
name         0
regions      0
start_dt     0
finish_dt    0
dtype: int64
In [16]:
final_ab_new_users.isna().sum()
Out[16]:
user_id       0
first_date    0
region        0
device        0
dtype: int64
In [17]:
final_ab_participants.isna().sum()
Out[17]:
user_id    0
group      0
ab_test    0
dtype: int64
In [18]:
final_ab_events_nan = final_ab_events[final_ab_events['details'].isna()]
final_ab_events_nan
Out[18]:
user_id event_dt event_name details
62740 2E1BF1D4C37EA01F 2020-12-07 09:05:47 product_cart NaN
62741 50734A22C0C63768 2020-12-07 13:24:03 product_cart NaN
62742 5EB159DA9DC94DBA 2020-12-07 22:54:02 product_cart NaN
62743 084A22B980BA8169 2020-12-07 15:25:55 product_cart NaN
62744 0FC21E6F8FAA8DEC 2020-12-07 06:56:27 product_cart NaN
... ... ... ... ...
440312 245E85F65C358E08 2020-12-30 19:35:55 login NaN
440313 9385A108F5A0A7A7 2020-12-30 10:54:15 login NaN
440314 DB650B7559AC6EAC 2020-12-30 10:59:09 login NaN
440315 F80C9BDDEA02E53C 2020-12-30 09:53:39 login NaN
440316 7AEC61159B672CC5 2020-12-30 11:36:13 login NaN

377577 rows × 4 columns

In [19]:
final_ab_events_nan['event_name'].unique()
Out[19]:
array(['product_cart', 'product_page', 'login'], dtype=object)
In [20]:
final_ab_events['event_name'].unique()
Out[20]:
array(['purchase', 'product_cart', 'product_page', 'login'], dtype=object)

Пропущенные значения по следующим типам события: product_cart, product_page, login. отсутствуют пропуски в типе событий purchase. В описании данных указано: " столбце details хранятся дополнительные данные о событии. Например, для покупок, purchase, в этом поле хранится стоимость покупки в долларах". Следовательно, по типам событий product_cart, product_page, login нет дополнительной информации, поэтому в датасете пропущеные значения. Пропуски зависят от данных. Относятся к неслучайному типу, поэтому мы их оставляем.

Исследовать на наличие дубликатов

In [21]:
final_ab_events.duplicated().sum()
Out[21]:
0
In [22]:
ab_project_marketing_events.duplicated().sum()
Out[22]:
0
In [23]:
final_ab_new_users.duplicated().sum()
Out[23]:
0
In [24]:
final_ab_participants.duplicated().sum()
Out[24]:
0

В данных не имеется явных дубликатов.

Оценить корректность проведения теста.

Проверить соответствие данных требованиям технического задания. Проверить корректность всех пунктов технического задания.

In [25]:
#приводим данные к нужному типу
final_ab_events['event_dt'] = pd.to_datetime(final_ab_events['event_dt'])

ab_project_marketing_events['start_dt'] = pd.to_datetime(ab_project_marketing_events['start_dt'])
ab_project_marketing_events['finish_dt'] = pd.to_datetime(ab_project_marketing_events['finish_dt'])

final_ab_new_users['first_date'] = pd.to_datetime(final_ab_new_users['first_date'])


print(final_ab_events.dtypes)
print(ab_project_marketing_events.dtypes)
print(final_ab_new_users.dtypes)
user_id               object
event_dt      datetime64[ns]
event_name            object
details              float64
dtype: object
name                 object
regions              object
start_dt     datetime64[ns]
finish_dt    datetime64[ns]
dtype: object
user_id               object
first_date    datetime64[ns]
region                object
device                object
dtype: object

Теперь типы данных во всех столбцах соответствуют нужным типам данных

In [26]:
#объединяем датасет с новыми пользователями с датасетом с действиями пользователей
df1 = pd.merge(final_ab_new_users, final_ab_events, on='user_id', how='left') 
df1
Out[26]:
user_id first_date region device event_dt event_name details
0 D72A72121175D8BE 2020-12-07 EU PC 2020-12-07 21:52:10 product_page NaN
1 D72A72121175D8BE 2020-12-07 EU PC 2020-12-07 21:52:07 login NaN
2 F1C668619DFE6E65 2020-12-07 N.America Android 2020-12-07 16:38:09 product_page NaN
3 F1C668619DFE6E65 2020-12-07 N.America Android 2020-12-08 02:02:34 product_page NaN
4 F1C668619DFE6E65 2020-12-07 N.America Android 2020-12-23 14:35:41 product_page NaN
... ... ... ... ... ... ... ...
443342 1C7D23927835213F 2020-12-20 EU iPhone 2020-12-23 15:47:23 login NaN
443343 1C7D23927835213F 2020-12-20 EU iPhone 2020-12-25 12:31:04 login NaN
443344 1C7D23927835213F 2020-12-20 EU iPhone 2020-12-27 03:51:35 login NaN
443345 8F04273BB2860229 2020-12-20 EU Android 2020-12-20 03:17:17 product_cart NaN
443346 8F04273BB2860229 2020-12-20 EU Android 2020-12-20 03:17:17 login NaN

443347 rows × 7 columns

In [27]:
#объединяем новый датасет пользователей и действий с датасетом участников теста
df = pd.merge(df1, final_ab_participants, on='user_id', how='left') 
df
Out[27]:
user_id first_date region device event_dt event_name details group ab_test
0 D72A72121175D8BE 2020-12-07 EU PC 2020-12-07 21:52:10 product_page NaN A recommender_system_test
1 D72A72121175D8BE 2020-12-07 EU PC 2020-12-07 21:52:07 login NaN A recommender_system_test
2 F1C668619DFE6E65 2020-12-07 N.America Android 2020-12-07 16:38:09 product_page NaN NaN NaN
3 F1C668619DFE6E65 2020-12-07 N.America Android 2020-12-08 02:02:34 product_page NaN NaN NaN
4 F1C668619DFE6E65 2020-12-07 N.America Android 2020-12-23 14:35:41 product_page NaN NaN NaN
... ... ... ... ... ... ... ... ... ...
449951 1C7D23927835213F 2020-12-20 EU iPhone 2020-12-23 15:47:23 login NaN B interface_eu_test
449952 1C7D23927835213F 2020-12-20 EU iPhone 2020-12-25 12:31:04 login NaN B interface_eu_test
449953 1C7D23927835213F 2020-12-20 EU iPhone 2020-12-27 03:51:35 login NaN B interface_eu_test
449954 8F04273BB2860229 2020-12-20 EU Android 2020-12-20 03:17:17 product_cart NaN NaN NaN
449955 8F04273BB2860229 2020-12-20 EU Android 2020-12-20 03:17:17 login NaN NaN NaN

449956 rows × 9 columns

In [28]:
df.isna().sum()
Out[28]:
user_id            0
first_date         0
region             0
device             0
event_dt        3745
event_name      3745
details       386368
group         339588
ab_test       339588
dtype: int64

3745 пропусков в event_dt и event_name в связи с отсутствием событий у некоторых пользователей. 386368 пропусков в графе details в связи с тем, что у некоторых типов событий нет дополнительной информации. 339588 пропусков в group и ab_test в связи с тем, что не все пользователи попали в тесты. Пропуски неслучайны и зависят от данных, поэтому пока их не трогаем.

В данных не хватает событий с 31 декабря по 4 января. Возможно, из-за выходных дней. С отсутствующими данными ничего сделать не сможем.

In [29]:
df
Out[29]:
user_id first_date region device event_dt event_name details group ab_test
0 D72A72121175D8BE 2020-12-07 EU PC 2020-12-07 21:52:10 product_page NaN A recommender_system_test
1 D72A72121175D8BE 2020-12-07 EU PC 2020-12-07 21:52:07 login NaN A recommender_system_test
2 F1C668619DFE6E65 2020-12-07 N.America Android 2020-12-07 16:38:09 product_page NaN NaN NaN
3 F1C668619DFE6E65 2020-12-07 N.America Android 2020-12-08 02:02:34 product_page NaN NaN NaN
4 F1C668619DFE6E65 2020-12-07 N.America Android 2020-12-23 14:35:41 product_page NaN NaN NaN
... ... ... ... ... ... ... ... ... ...
449951 1C7D23927835213F 2020-12-20 EU iPhone 2020-12-23 15:47:23 login NaN B interface_eu_test
449952 1C7D23927835213F 2020-12-20 EU iPhone 2020-12-25 12:31:04 login NaN B interface_eu_test
449953 1C7D23927835213F 2020-12-20 EU iPhone 2020-12-27 03:51:35 login NaN B interface_eu_test
449954 8F04273BB2860229 2020-12-20 EU Android 2020-12-20 03:17:17 product_cart NaN NaN NaN
449955 8F04273BB2860229 2020-12-20 EU Android 2020-12-20 03:17:17 login NaN NaN NaN

449956 rows × 9 columns

In [30]:
df_eu = df.query('region == "EU" and "2020-12-07" <= first_date <= "2020-12-21"')
cnt_users_eu = df_eu['user_id'].nunique() #количество пользователей из Европы
cnt_users_eu
Out[30]:
42340
In [31]:
df_eu_rst = df_eu[df_eu['ab_test'] == 'recommender_system_test'] 
cnt_users_eu_rst = df_eu_rst['user_id'].nunique() #количество соответствующих техзаданию пользователей: EU и участники теста
cnt_users_eu_rst
Out[31]:
6351
In [32]:
percent = cnt_users_eu_rst * 100 / cnt_users_eu 
percent
Out[32]:
15.0

Выборка примерно соответствует техническому заданию.

In [33]:
df_eu_rst['first_date'].min()
Out[33]:
Timestamp('2020-12-07 00:00:00')
In [34]:
df_eu_rst['first_date'].max()
Out[34]:
Timestamp('2020-12-21 00:00:00')
In [35]:
df_eu_rst['event_dt'].min()
Out[35]:
Timestamp('2020-12-07 00:05:57')
In [36]:
df_eu_rst['event_dt'].max()
Out[36]:
Timestamp('2020-12-30 12:42:57')

Аудиторию теста. Удостовериться, что нет пересечений с конкурирующим тестом и нет пользователей, участвующих в двух группах теста одновременно. Проверить равномерность распределения по тестовым группам и правильность их формирования.

In [37]:
df['ab_test'].unique()
Out[37]:
array(['recommender_system_test', nan, 'interface_eu_test'], dtype=object)
In [38]:
df2 = df_eu.groupby('user_id').agg({"ab_test":"nunique"}).query('ab_test > 1').reset_index()
df2.columns = ['user_id', 'cnt']
print(df2.head())
print()
cnt_users_both_test = df2['user_id'].nunique()
print(cnt_users_both_test)
print()
print(round(cnt_users_both_test * 100 / cnt_users_eu_rst, 2))
            user_id  cnt
0  001064FEAAB631A1    2
1  00341D8401F0F665    2
2  003B6786B4FF5B03    2
3  0082295A41A867B5    2
4  00E68F103C66C1F7    2

1602

25.22

В оба теста попали 1602 пользователя (23,91%). Значительная доля, поэтому удалить их не можем. Необходимо посчитать количество пользователей теста interface_eu_test из группы В, группу А считать не будем, т.к. это контрольная группа, на нее нет воздействия. Количество пользователей теста interface_eu_test из группы А оставляем

In [39]:
#создаем таблицу пользователей, попавших в оба теста
both_test = df2.merge(final_ab_participants[['user_id', 'group', 'ab_test']], on='user_id', how='left')
both_test
Out[39]:
user_id cnt group ab_test
0 001064FEAAB631A1 2 B recommender_system_test
1 001064FEAAB631A1 2 B interface_eu_test
2 00341D8401F0F665 2 A recommender_system_test
3 00341D8401F0F665 2 A interface_eu_test
4 003B6786B4FF5B03 2 A recommender_system_test
... ... ... ... ...
3199 FFC53FD45DDA5EE8 2 A interface_eu_test
3200 FFED90241D04503F 2 B recommender_system_test
3201 FFED90241D04503F 2 B interface_eu_test
3202 FFF28D02B1EACBE1 2 B recommender_system_test
3203 FFF28D02B1EACBE1 2 A interface_eu_test

3204 rows × 4 columns

In [40]:
#таблица пользователей группы В теста interface_eu_test, попавших в оба теста
test_iet_b = both_test.query('ab_test == "interface_eu_test" and group == "B"')
cnt_test_iet_b = test_iet_b['user_id'].nunique()
print(cnt_test_iet_b)
print()
print(round((cnt_test_iet_b * 100 / cnt_users_eu_rst), 2))
783

12.33

Количество пользователей группы В теста interface_eu_test, попавших в оба теста, - 783 пользователя, 11,68 %. Проверим распределение пользователей группы В по группам нашего теста.

In [41]:
test_iet_b
Out[41]:
user_id cnt group ab_test
1 001064FEAAB631A1 2 B interface_eu_test
7 0082295A41A867B5 2 B interface_eu_test
9 00E68F103C66C1F7 2 B interface_eu_test
13 010DB4614355A4BB 2 B interface_eu_test
19 020A95B66F363AFB 2 B interface_eu_test
... ... ... ... ...
3187 FEC0BCA6C323872F 2 B interface_eu_test
3189 FEE7040D7D2F2968 2 B interface_eu_test
3195 FF7BE2897FC0380D 2 B interface_eu_test
3197 FF9A81323FA67D6E 2 B interface_eu_test
3201 FFED90241D04503F 2 B interface_eu_test

783 rows × 4 columns

In [42]:
#создаем датасет с пользователями группы В другого теста, попавших в оба теста, с разбивкой по группам А и В нашего теста
test_iet_b_with_rst = test_iet_b.merge(both_test[['user_id', 'group', 'ab_test']], on='user_id', how='left')
test_iet_b_with_rst.columns = ['user_id', 'cnt', 'group_iet', 'test2', 'group_rst', 'test1']
test_iet_b_with_rst
Out[42]:
user_id cnt group_iet test2 group_rst test1
0 001064FEAAB631A1 2 B interface_eu_test B recommender_system_test
1 001064FEAAB631A1 2 B interface_eu_test B interface_eu_test
2 0082295A41A867B5 2 B interface_eu_test A recommender_system_test
3 0082295A41A867B5 2 B interface_eu_test B interface_eu_test
4 00E68F103C66C1F7 2 B interface_eu_test A recommender_system_test
... ... ... ... ... ... ...
1561 FF7BE2897FC0380D 2 B interface_eu_test B interface_eu_test
1562 FF9A81323FA67D6E 2 B interface_eu_test B recommender_system_test
1563 FF9A81323FA67D6E 2 B interface_eu_test B interface_eu_test
1564 FFED90241D04503F 2 B interface_eu_test B recommender_system_test
1565 FFED90241D04503F 2 B interface_eu_test B interface_eu_test

1566 rows × 6 columns

In [43]:
test_rst_a_both = test_iet_b_with_rst.query('test1 == "recommender_system_test" and group_rst == "A"')
cnt_test_rst_a_both = test_rst_a_both['user_id'].nunique() #пользователи группы А обоих тестов
print(cnt_test_rst_a_both)
print()

users_eu_rst_a = df_eu_rst[df_eu_rst['group'] == "A"] 
cnt_users_eu_rst_a = users_eu_rst_a['user_id'].nunique() #пользователи группы А нашего теста
print(cnt_users_eu_rst_a)
print()

print(round(cnt_test_rst_a_both * 100 / cnt_users_eu_rst_a, 2))
439

3634

12.08
In [44]:
test_rst_b_both = test_iet_b_with_rst.query('test1 == "recommender_system_test" and group_rst == "B"')
cnt_test_rst_b_both = test_rst_b_both['user_id'].nunique() #пользователи группы В обоих тестов
print(cnt_test_rst_b_both)
print()

users_eu_rst_b = df_eu_rst[df_eu_rst['group'] == "B"] 
cnt_users_eu_rst_b = users_eu_rst_b['user_id'].nunique() #пользователи группы B нашего теста
print(cnt_users_eu_rst_b)
print()

print(round(cnt_test_rst_b_both * 100 / cnt_users_eu_rst_b, 2))
344

2717

12.66

Разница в распределении пользователей по группам А и В нашего теста менее 1 процента, распределение равномерное, можем оставить пользователей группы B теста interface_eu_test из группы В. Пользователи группы B теста interface_eu_test не окажут сильное влияние на результаты.

Время проведения теста. Убедиться, что оно не совпадает с маркетинговыми и другими активностями.

In [45]:
ab_project_marketing_events
Out[45]:
name regions start_dt finish_dt
0 Christmas&New Year Promo EU, N.America 2020-12-25 2021-01-03
1 St. Valentine's Day Giveaway EU, CIS, APAC, N.America 2020-02-14 2020-02-16
2 St. Patric's Day Promo EU, N.America 2020-03-17 2020-03-19
3 Easter Promo EU, CIS, APAC, N.America 2020-04-12 2020-04-19
4 4th of July Promo N.America 2020-07-04 2020-07-11
5 Black Friday Ads Campaign EU, CIS, APAC, N.America 2020-11-26 2020-12-01
6 Chinese New Year Promo APAC 2020-01-25 2020-02-07
7 Labor day (May 1st) Ads Campaign EU, CIS, APAC 2020-05-01 2020-05-03
8 International Women's Day Promo EU, CIS, APAC 2020-03-08 2020-03-10
9 Victory Day CIS (May 9th) Event CIS 2020-05-09 2020-05-11
10 CIS New Year Gift Lottery CIS 2020-12-30 2021-01-07
11 Dragon Boat Festival Giveaway APAC 2020-06-25 2020-07-01
12 Single's Day Gift Promo APAC 2020-11-11 2020-11-12
13 Chinese Moon Festival APAC 2020-10-01 2020-10-07
In [46]:
marketing_events_eu = ab_project_marketing_events.query('start_dt <= "2021-01-04" and finish_dt >= "2020-12-07"')
marketing_events_eu
Out[46]:
name regions start_dt finish_dt
0 Christmas&New Year Promo EU, N.America 2020-12-25 2021-01-03
10 CIS New Year Gift Lottery CIS 2020-12-30 2021-01-07

В Европе проводилась только одна акция, которая могла оказать влияние на действия пользователей.

In [47]:
df_promo = df_eu_rst.query('"2020-12-25" <= event_dt <= "2021-01-03"')
df_promo
Out[47]:
user_id first_date region device event_dt event_name details group ab_test
156 DD4352CDCF8C3D57 2020-12-07 EU Android 2020-12-30 12:42:57 product_page NaN B recommender_system_test
168 DD4352CDCF8C3D57 2020-12-07 EU Android 2020-12-30 12:42:56 login NaN B recommender_system_test
1377 75845C83258FBF73 2020-12-07 EU Android 2020-12-30 06:42:52 product_cart NaN B recommender_system_test
1381 75845C83258FBF73 2020-12-07 EU Android 2020-12-30 06:42:52 login NaN B recommender_system_test
1677 6A581C74EF4D5F44 2020-12-07 EU iPhone 2020-12-29 21:46:48 login NaN B recommender_system_test
... ... ... ... ... ... ... ... ... ...
449096 549A5092FD1BD5D9 2020-12-20 EU iPhone 2020-12-25 21:50:57 login NaN A recommender_system_test
449801 574ACBC674BC385D 2020-12-20 EU Mac 2020-12-26 04:55:27 purchase 4.99 A recommender_system_test
449803 574ACBC674BC385D 2020-12-20 EU Mac 2020-12-26 04:55:29 product_cart NaN A recommender_system_test
449805 574ACBC674BC385D 2020-12-20 EU Mac 2020-12-26 04:55:28 product_page NaN A recommender_system_test
449807 574ACBC674BC385D 2020-12-20 EU Mac 2020-12-26 04:55:27 login NaN A recommender_system_test

3164 rows × 9 columns

In [48]:
df_promo['event_dt'] = df_promo['event_dt'].dt.round('24H')
df_promo
C:\__MACOSX\ANACONDA\lib\site-packages\ipykernel_launcher.py:1: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  """Entry point for launching an IPython kernel.
Out[48]:
user_id first_date region device event_dt event_name details group ab_test
156 DD4352CDCF8C3D57 2020-12-07 EU Android 2020-12-31 product_page NaN B recommender_system_test
168 DD4352CDCF8C3D57 2020-12-07 EU Android 2020-12-31 login NaN B recommender_system_test
1377 75845C83258FBF73 2020-12-07 EU Android 2020-12-30 product_cart NaN B recommender_system_test
1381 75845C83258FBF73 2020-12-07 EU Android 2020-12-30 login NaN B recommender_system_test
1677 6A581C74EF4D5F44 2020-12-07 EU iPhone 2020-12-30 login NaN B recommender_system_test
... ... ... ... ... ... ... ... ... ...
449096 549A5092FD1BD5D9 2020-12-20 EU iPhone 2020-12-26 login NaN A recommender_system_test
449801 574ACBC674BC385D 2020-12-20 EU Mac 2020-12-26 purchase 4.99 A recommender_system_test
449803 574ACBC674BC385D 2020-12-20 EU Mac 2020-12-26 product_cart NaN A recommender_system_test
449805 574ACBC674BC385D 2020-12-20 EU Mac 2020-12-26 product_page NaN A recommender_system_test
449807 574ACBC674BC385D 2020-12-20 EU Mac 2020-12-26 login NaN A recommender_system_test

3164 rows × 9 columns

In [49]:
df_promo.pivot_table(
    index='event_dt', 
    values='user_id', 
    aggfunc='nunique' 
).plot(figsize=(15, 8), grid=True)
plt.xlabel('Дата маркетинговой акции')
plt.title('Количество пользователей')
plt.show()

Судя по графику акция оказала влияние, но не сильное, т.к. резких переходов и скачков не было.

In [50]:
df_eu_rst_nan = df_eu_rst[df_eu_rst['event_name'].isna()]
cnt_nan_users = df_eu_rst_nan.groupby('group')['user_id'].nunique()
cnt_nan_users
Out[50]:
group
A    1030
B    1840
Name: user_id, dtype: int64
In [51]:
#количество пользователтей, которые не совершали действий
cnt_nan_users_all = cnt_nan_users[0]+cnt_nan_users[1]
cnt_nan_users_all
Out[51]:
2870
In [52]:
#доля пользователей, которые не совершали действий
print(round((cnt_nan_users_all * 100 / cnt_users_eu_rst), 2))
45.19
In [53]:
#доля пользователей, которые не совершали действий группы А
print(round((cnt_nan_users[0]*100/cnt_users_eu_rst_a), 2))
28.34
In [54]:
#доля пользователей, которые не совершали действий группы А
print(round((cnt_nan_users[1]*100/cnt_users_eu_rst_b), 2))
67.72
In [55]:
#удаляем пользователей, которые не совершали действий
df_eu_rst = df_eu_rst.dropna(subset=['event_name', 'event_dt'])
cnt_activ_users = df_eu_rst['user_id'].nunique()
cnt_activ_users
Out[55]:
3481
In [56]:
print(round((cnt_activ_users * 100 / cnt_users_eu_rst), 2))
54.81

После удаления пользователей, которые не совершали действий, мы потеряли 45,2% данных по пользователям. У нас осталось 54,8% данных.

In [57]:
df_eu_rst
Out[57]:
user_id first_date region device event_dt event_name details group ab_test
0 D72A72121175D8BE 2020-12-07 EU PC 2020-12-07 21:52:10 product_page NaN A recommender_system_test
1 D72A72121175D8BE 2020-12-07 EU PC 2020-12-07 21:52:07 login NaN A recommender_system_test
146 DD4352CDCF8C3D57 2020-12-07 EU Android 2020-12-07 15:32:54 product_page NaN B recommender_system_test
148 DD4352CDCF8C3D57 2020-12-07 EU Android 2020-12-08 08:29:31 product_page NaN B recommender_system_test
150 DD4352CDCF8C3D57 2020-12-07 EU Android 2020-12-10 18:18:27 product_page NaN B recommender_system_test
... ... ... ... ... ... ... ... ... ...
449812 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-21 22:28:29 product_page NaN A recommender_system_test
449813 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-24 09:12:51 product_page NaN A recommender_system_test
449814 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-20 20:58:25 login NaN A recommender_system_test
449815 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-21 22:28:29 login NaN A recommender_system_test
449816 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-24 09:12:49 login NaN A recommender_system_test

23420 rows × 9 columns

In [58]:
df_eu_rst['event_dt'] = pd.to_datetime(df_eu_rst['event_dt']).dt.date
df_eu_rst['event_dt'] = df_eu_rst['event_dt'].astype('datetime64')
In [59]:
df_eu_rst
Out[59]:
user_id first_date region device event_dt event_name details group ab_test
0 D72A72121175D8BE 2020-12-07 EU PC 2020-12-07 product_page NaN A recommender_system_test
1 D72A72121175D8BE 2020-12-07 EU PC 2020-12-07 login NaN A recommender_system_test
146 DD4352CDCF8C3D57 2020-12-07 EU Android 2020-12-07 product_page NaN B recommender_system_test
148 DD4352CDCF8C3D57 2020-12-07 EU Android 2020-12-08 product_page NaN B recommender_system_test
150 DD4352CDCF8C3D57 2020-12-07 EU Android 2020-12-10 product_page NaN B recommender_system_test
... ... ... ... ... ... ... ... ... ...
449812 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-21 product_page NaN A recommender_system_test
449813 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-24 product_page NaN A recommender_system_test
449814 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-20 login NaN A recommender_system_test
449815 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-21 login NaN A recommender_system_test
449816 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-24 login NaN A recommender_system_test

23420 rows × 9 columns

In [60]:
#ищем возраст событий
df_eu_rst['cnt_days'] = df_eu_rst['event_dt'] - df_eu_rst['first_date']
In [61]:
df_eu_rst.dtypes
Out[61]:
user_id                object
first_date     datetime64[ns]
region                 object
device                 object
event_dt       datetime64[ns]
event_name             object
details               float64
group                  object
ab_test                object
cnt_days      timedelta64[ns]
dtype: object
In [62]:
#количество пользователей по дням совершения действий
cnt_days = df_eu_rst.groupby('cnt_days')['user_id'].nunique()
cnt_days
Out[62]:
cnt_days
0 days     3467
1 days     1625
2 days     1116
3 days      774
4 days      629
5 days      517
6 days      457
7 days      410
8 days      337
9 days      267
10 days     217
11 days     147
12 days     151
13 days     113
14 days      96
15 days      79
16 days      46
17 days      36
18 days      36
19 days      26
20 days      29
21 days      14
22 days      14
23 days       2
Name: user_id, dtype: int64
In [63]:
cnt_days.plot(kind='bar', figsize=(15, 8), grid=True)
plt.xlabel('Порядок дней')
plt.title('Количество пользователей по дням совершения действий')
plt.show()
In [64]:
df_eu_rst['cnt_days'] = df_eu_rst['cnt_days'].astype('timedelta64[D]')
df_eu_rst['cnt_days'] = df_eu_rst['cnt_days'].astype('int64')
In [65]:
data = df_eu_rst.query('cnt_days <= 14')
data
Out[65]:
user_id first_date region device event_dt event_name details group ab_test cnt_days
0 D72A72121175D8BE 2020-12-07 EU PC 2020-12-07 product_page NaN A recommender_system_test 0
1 D72A72121175D8BE 2020-12-07 EU PC 2020-12-07 login NaN A recommender_system_test 0
146 DD4352CDCF8C3D57 2020-12-07 EU Android 2020-12-07 product_page NaN B recommender_system_test 0
148 DD4352CDCF8C3D57 2020-12-07 EU Android 2020-12-08 product_page NaN B recommender_system_test 1
150 DD4352CDCF8C3D57 2020-12-07 EU Android 2020-12-10 product_page NaN B recommender_system_test 3
... ... ... ... ... ... ... ... ... ... ...
449812 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-21 product_page NaN A recommender_system_test 1
449813 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-24 product_page NaN A recommender_system_test 4
449814 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-20 login NaN A recommender_system_test 0
449815 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-21 login NaN A recommender_system_test 1
449816 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-24 login NaN A recommender_system_test 4

22828 rows × 10 columns

In [66]:
cnt_days = data.groupby('cnt_days')['user_id'].nunique()
display(cnt_days)
cnt_days.plot(kind='bar', figsize=(15, 8), grid=True)
plt.xlabel('Порядок дней')
plt.title('Количество пользователей по дням совершения действий')
plt.show()
cnt_days
0     3467
1     1625
2     1116
3      774
4      629
5      517
6      457
7      410
8      337
9      267
10     217
11     147
12     151
13     113
14      96
Name: user_id, dtype: int64

Основное количество пользователей совершали действия в первые дни.

In [67]:
#проверка на пересечение пользоваталей внутри теста
data.groupby('user_id').agg({'group': 'nunique'}).query('group > 1')
Out[67]:
group
user_id

В оба теста попали 1602 пользователя (23,91%). Значительная доля, поэтому удалить их не можем. Необходимо посчитать количество пользователей теста interface_eu_test из группы В, группу А считать не будем, т.к. это контрольная группа, на нее нет воздействия. Количество пользователей теста interface_eu_test из группы А оставляем

Количество пользователей группы В теста interface_eu_test, попавших в оба теста, - 783 пользователя, 11,68 %. Проверим распределение пользователей группы В по группам нашего теста.

Разница в распределении пользователей по группам А и В нашего теста менее 1 процента, распределение равномерное, можем оставить пользователей группы B теста interface_eu_test из группы В. Пользователи группы B теста interface_eu_test не окажут сильное влияние на результаты.

Маркетинговая акция не оказала сильное влияние на тест.

Основное количество пользователей совершали действия в первые дни.

Провести исследовательский анализ данных:

Количество событий на пользователя одинаково распределены в выборках?

In [68]:
cnt_event = data.groupby('group')['event_name'].count().reset_index()
cnt_users = data.groupby('group')['user_id'].nunique().reset_index()
In [69]:
print(cnt_event)
print(cnt_users)
  group  event_name
0     A       17977
1     B        4851
  group  user_id
0     A     2604
1     B      877
In [70]:
cnt_event['one_user'] = round((cnt_event['event_name'] / cnt_users['user_id']), 0)
print(cnt_event)
  group  event_name  one_user
0     A       17977       7.0
1     B        4851       6.0

В группе А на 1 пользователя приходится 7 событий, в группе В на одного пользователя 6 событий. Распределение среднего количества событий на пользователя в выборках отличается на 16%.

In [71]:
data
Out[71]:
user_id first_date region device event_dt event_name details group ab_test cnt_days
0 D72A72121175D8BE 2020-12-07 EU PC 2020-12-07 product_page NaN A recommender_system_test 0
1 D72A72121175D8BE 2020-12-07 EU PC 2020-12-07 login NaN A recommender_system_test 0
146 DD4352CDCF8C3D57 2020-12-07 EU Android 2020-12-07 product_page NaN B recommender_system_test 0
148 DD4352CDCF8C3D57 2020-12-07 EU Android 2020-12-08 product_page NaN B recommender_system_test 1
150 DD4352CDCF8C3D57 2020-12-07 EU Android 2020-12-10 product_page NaN B recommender_system_test 3
... ... ... ... ... ... ... ... ... ... ...
449812 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-21 product_page NaN A recommender_system_test 1
449813 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-24 product_page NaN A recommender_system_test 4
449814 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-20 login NaN A recommender_system_test 0
449815 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-21 login NaN A recommender_system_test 1
449816 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-24 login NaN A recommender_system_test 4

22828 rows × 10 columns

In [72]:
reg = data.groupby(['first_date', 'group'])['user_id'].nunique().reset_index()
reg.columns = ['first_date', 'group', 'cnt']
reg
Out[72]:
first_date group cnt
0 2020-12-07 A 154
1 2020-12-07 B 171
2 2020-12-08 A 90
3 2020-12-08 B 42
4 2020-12-09 A 81
5 2020-12-09 B 80
6 2020-12-10 A 60
7 2020-12-10 B 32
8 2020-12-11 A 87
9 2020-12-11 B 19
10 2020-12-12 A 61
11 2020-12-12 B 50
12 2020-12-13 A 52
13 2020-12-13 B 14
14 2020-12-14 A 366
15 2020-12-14 B 68
16 2020-12-15 A 202
17 2020-12-15 B 31
18 2020-12-16 A 160
19 2020-12-16 B 97
20 2020-12-17 A 202
21 2020-12-17 B 36
22 2020-12-18 A 210
23 2020-12-18 B 48
24 2020-12-19 A 226
25 2020-12-19 B 43
26 2020-12-20 A 255
27 2020-12-20 B 63
28 2020-12-21 A 398
29 2020-12-21 B 83
In [73]:
reg['first_date'] = reg['first_date'].dt.date
In [74]:
reg.groupby('group')['cnt'].sum()
Out[74]:
group
A    2604
B     877
Name: cnt, dtype: int64
In [75]:
ax = sns.barplot(x='first_date',
            y='cnt',
            hue="group",
            data=reg   
)
plt.gcf().set_size_inches(15,8)
sns.set(font_scale=2)
plt.xticks(rotation=90)
ax.set_title('Динамика количества пользователей по дням регистрации в разрезе групп')
ax.set(xlabel='Дата регистрации', ylabel='Количество пользователей');

Основное количество пользователей группы А регистрировались с 14 по 21 декабря. Максимальное количество пользователей группы В регистрировались 7 декабря..Пользователи регистрировались неравномерно. Пользователей группы В зарегистрировалось почти в 3 раза меньше.

Как число событий в выборках распределено по дням?

In [76]:
cnt_events_days = data.groupby(['cnt_days', 'group'])['event_name'].count().reset_index()
cnt_events_days
Out[76]:
cnt_days group event_name
0 0 A 5892
1 0 B 1827
2 1 A 2817
3 1 B 742
4 2 A 1979
5 2 B 472
6 3 A 1384
7 3 B 328
8 4 A 1123
9 4 B 294
10 5 A 958
11 5 B 191
12 6 A 814
13 6 B 185
14 7 A 719
15 7 B 205
16 8 A 579
17 8 B 146
18 9 A 468
19 9 B 110
20 10 A 396
21 10 B 85
22 11 A 256
23 11 B 66
24 12 A 256
25 12 B 85
26 13 A 194
27 13 B 49
28 14 A 142
29 14 B 66
In [77]:
cnt_events_days.groupby('group')['event_name'].sum()
Out[77]:
group
A    17977
B     4851
Name: event_name, dtype: int64
In [78]:
ax = sns.barplot(x='cnt_days',
            y='event_name',
            hue="group",
            data=cnt_events_days   
)
plt.gcf().set_size_inches(15,8)
sns.set(font_scale=2)
plt.xticks(rotation=90)
ax.set_title('Динамика количества событий по дням в разрезе групп')
ax.set(xlabel='Порядковый номер дня', ylabel='Количество событий');

Основное количество событий совершались в первые дни по двум группам. События пользователями группы В совершались в 3,7 раза меньше, чем пользователями группы А

In [79]:
mean_sum = data.groupby('group')['details'].sum().reset_index()
mean_sum
Out[79]:
group details
0 A 57705.01
1 B 14268.76
In [80]:
mean_sum.plot(x='group', y='details', kind='bar', figsize=(10, 4), grid=True)
plt.xlabel('Группы')
plt.title('Средняя сумма, потраченная пользователями по группам')
plt.show()

Средняя сумма, потраченная пользователями группы В, в 4 раза меньше потраченной средней суммы группы А.

In [81]:
data['device'].unique()
Out[81]:
array(['PC', 'Android', 'iPhone', 'Mac'], dtype=object)
In [82]:
data
Out[82]:
user_id first_date region device event_dt event_name details group ab_test cnt_days
0 D72A72121175D8BE 2020-12-07 EU PC 2020-12-07 product_page NaN A recommender_system_test 0
1 D72A72121175D8BE 2020-12-07 EU PC 2020-12-07 login NaN A recommender_system_test 0
146 DD4352CDCF8C3D57 2020-12-07 EU Android 2020-12-07 product_page NaN B recommender_system_test 0
148 DD4352CDCF8C3D57 2020-12-07 EU Android 2020-12-08 product_page NaN B recommender_system_test 1
150 DD4352CDCF8C3D57 2020-12-07 EU Android 2020-12-10 product_page NaN B recommender_system_test 3
... ... ... ... ... ... ... ... ... ... ...
449812 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-21 product_page NaN A recommender_system_test 1
449813 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-24 product_page NaN A recommender_system_test 4
449814 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-20 login NaN A recommender_system_test 0
449815 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-21 login NaN A recommender_system_test 1
449816 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-24 login NaN A recommender_system_test 4

22828 rows × 10 columns

In [83]:
cnt_device = data.groupby(['device', 'group'])['user_id'].nunique().reset_index()
cnt_device
Out[83]:
device group user_id
0 Android A 1139
1 Android B 405
2 Mac A 255
3 Mac B 74
4 PC A 689
5 PC B 212
6 iPhone A 521
7 iPhone B 186
In [84]:
ax = sns.barplot(x='device',
            y='user_id',
            hue="group",
            data=cnt_device   
)
plt.gcf().set_size_inches(15,8)
sns.set(font_scale=2)
plt.xticks(rotation=90)
ax.set_title('Распределение пользователей групп по устройствам')
ax.set(xlabel='Устройство', ylabel='Количество пользователей');

Пользователи теста в основном пользуются Android.
Пользователей группы В, использующих Android, в 2,8 раза меньше чем группы А;
пользователей группы В, использующих Mac, в 3,5 раза меньше чем группы А;
пользователей группы В, использующих PC, в 3,2 раза меньше чем группы А; пользователей группы В, использующих iPhone, в 2,8 раза меньше чем группы А. Одинаковое распределение пользователей использующих Android и iPhone.

In [85]:
#количество событий по дням
type_events_cnt_days = data.groupby(['cnt_days', 'event_name'])['user_id'].nunique().reset_index()
type_events_cnt_days
Out[85]:
cnt_days event_name user_id
0 0 login 3459
1 0 product_cart 1020
2 0 product_page 2164
3 0 purchase 1076
4 1 login 1623
5 1 product_cart 471
6 1 product_page 990
7 1 purchase 475
8 2 login 1116
9 2 product_cart 322
10 2 product_page 682
11 2 purchase 331
12 3 login 774
13 3 product_cart 234
14 3 product_page 462
15 3 purchase 242
16 4 login 629
17 4 product_cart 183
18 4 product_page 411
19 4 purchase 194
20 5 login 517
21 5 product_cart 148
22 5 product_page 330
23 5 purchase 154
24 6 login 457
25 6 product_cart 126
26 6 product_page 275
27 6 purchase 141
28 7 login 410
29 7 product_cart 120
30 7 product_page 262
31 7 purchase 132
32 8 login 337
33 8 product_cart 100
34 8 product_page 198
35 8 purchase 90
36 9 login 267
37 9 product_cart 78
38 9 product_page 156
39 9 purchase 77
40 10 login 217
41 10 product_cart 66
42 10 product_page 135
43 10 purchase 63
44 11 login 147
45 11 product_cart 39
46 11 product_page 88
47 11 purchase 48
48 12 login 151
49 12 product_cart 39
50 12 product_page 100
51 12 purchase 51
52 13 login 113
53 13 product_cart 32
54 13 product_page 69
55 13 purchase 29
56 14 login 96
57 14 product_cart 32
58 14 product_page 60
59 14 purchase 20
In [86]:
ax = sns.barplot(x='cnt_days',
            y='user_id',
            hue="event_name",
            data=type_events_cnt_days   
)
plt.gcf().set_size_inches(15,8)
sns.set(font_scale=2)
plt.xticks(rotation=90)
ax.set_title('Динамика событий по дням')
ax.set(xlabel='Порядковый номер дня', ylabel='Количество пользователей');

Как меняется конверсия в воронке в выборках на разных этапах?

In [87]:
cnt_users_events_groups = data.pivot_table(index='event_name', columns='group', values='user_id',  aggfunc='nunique').reset_index()
cnt_users_events_groups = cnt_users_events_groups.reindex([0,2,1,3])
cnt_users_events_groups
Out[87]:
group event_name A B
0 login 2604 876
2 product_page 1685 493
1 product_cart 782 244
3 purchase 833 249
In [88]:
from plotly import graph_objects as go

fig = go.Figure()

fig.add_trace(go.Funnel(
    name = 'A',
    y = ["login", "product_page", "product_cart", "purchase"],
    x = [2747, 1780, 824, 872],
    textinfo = "value+percent initial"))

fig.add_trace(go.Funnel(
    name = 'B',
    orientation = "h",
    y = ["login", "product_page", "product_cart", "purchase"],
    x = [927, 523, 255, 256],
    textposition = "inside",
    textinfo = "value+percent initial"))

fig.show()

По группе А мы при первом переходе потеряли 35% пользователей, по группе В - 44% потеряли. При первом шаге на 9 % больше пользователей теряем в группе В.
Во втором переходе по группе А мы потеряли 70% от первого шага, по группе В - 72% потеряли. При втором шаге на 2% больше теряем по группе В.
В третьем переходе по группе А теряем 68% от первого шага (скорее всего, у пользователей есть возможность пропустить предыдущий шаг), по группе В - 72% потеряли. При 3 шаге на 2 процента больше теряем в группе В.

Какие особенности данных нужно учесть, прежде чем приступать к A/B-тестированию?

У нас отсутствуют данные с 31 декабря 2020 года по 4 января 2021 года.

Мы удалили значительную долю пользователей, которые не совершали события.

В оба теста попали 1602 пользователя (23,91%). Значительная доля, поэтому удалить их не можем. Необходимо посчитать количество пользователей теста interface_eu_test из группы В, группу А считать не будем, т.к. это контрольная группа, на нее нет воздействия. Количество пользователей теста interface_eu_test из группы А оставляем.

Количество пользователей группы В теста interface_eu_test, попавших в оба теста, - 783 пользователя, 11,68 %.

Разница в распределении пользователей по группам А и В нашего теста менее 1 процента, распределение равномерное, можем оставить пользователей группы B теста interface_eu_test из группы В. Пользователи группы B теста interface_eu_test не окажут сильное влияние на результаты.

В Европе проводилась только одна акция, которая совпала с днями проведения теста.

Оценить результаты A/B-тестирования

Что можно сказать про результаты A/В-тестирования?

По группе А мы при первом переходе потеряли 35% пользователей, по группе В - 44% потеряли. При первом шаге на 9 % больше пользователей теряем в группе В. Во втором переходе по группе А мы потеряли 70% от первого шага, по группе В - 72% потеряли. При втором шаге на 2% больше теряем по группе В. В третьем переходе по группе А теряем 68% от первого шага (скорее всего, у пользователей есть возможность пропустить предыдущий шаг), по группе В - 72% потеряли. При 3 шаге на 2 процента больше теряем в группе В.

За 14 дней с момента регистрации пользователи не показали улучшение каждой метрики не менее, чем на 10%

Проверьте статистическую разницу долей z-критерием.

Нулевая гипотеза:
Между группой А и группой В нет значимой разницы при регистрации.
Между группой А и группой В нет значимой разницы при просмотре карточки товара.
Между группой А и группой В нет значимой разницы при просмотре страницы товара.
Между группой А и группой В нет значимой разницы при покупке.

Альтернативная гипотеза:
Между группой А и группой В есть значимая разница при регистрации.
Между группой А и группой В есть значимая разница при просмотре карточки товара.
Между группой А и группой В есть значимая разница при просмотре страницы товара.
Между группой А и группой В есть значимая разница при покупке.

In [89]:
data
Out[89]:
user_id first_date region device event_dt event_name details group ab_test cnt_days
0 D72A72121175D8BE 2020-12-07 EU PC 2020-12-07 product_page NaN A recommender_system_test 0
1 D72A72121175D8BE 2020-12-07 EU PC 2020-12-07 login NaN A recommender_system_test 0
146 DD4352CDCF8C3D57 2020-12-07 EU Android 2020-12-07 product_page NaN B recommender_system_test 0
148 DD4352CDCF8C3D57 2020-12-07 EU Android 2020-12-08 product_page NaN B recommender_system_test 1
150 DD4352CDCF8C3D57 2020-12-07 EU Android 2020-12-10 product_page NaN B recommender_system_test 3
... ... ... ... ... ... ... ... ... ... ...
449812 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-21 product_page NaN A recommender_system_test 1
449813 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-24 product_page NaN A recommender_system_test 4
449814 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-20 login NaN A recommender_system_test 0
449815 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-21 login NaN A recommender_system_test 1
449816 0416B34D35C8C8B8 2020-12-20 EU Android 2020-12-24 login NaN A recommender_system_test 4

22828 rows × 10 columns

In [90]:
cnt_users_events_groups = data.pivot_table(index='event_name', columns='group', values='user_id', margins=True, aggfunc='nunique').reset_index()
cnt_users_events_groups
Out[90]:
group event_name A B All
0 login 2604 876 3480
1 product_cart 782 244 1026
2 product_page 1685 493 2178
3 purchase 833 249 1082
4 All 2604 877 3481
In [91]:
cnt_users_events_groups = cnt_users_events_groups.rename(columns = {'A': 'group_a', 'B': 'group_b'})
print(cnt_users_events_groups)
group    event_name  group_a  group_b   All
0             login     2604      876  3480
1      product_cart      782      244  1026
2      product_page     1685      493  2178
3          purchase      833      249  1082
4               All     2604      877  3481
In [92]:
df_ab = data.groupby('group').agg({'user_id':'nunique'}).reset_index()
df_ab = (df_ab.assign(idx=df_ab.groupby('group').cumcount())
                 .pivot_table(index='idx', columns='group',
                              values='user_id', aggfunc='sum')).reset_index(drop=True)
df_ab = df_ab.rename(columns = {'A': 'group_a', 'B': 'group_b'})
df_ab['all'] = df_ab['group_a']+df_ab['group_b']
df_ab
Out[92]:
group group_a group_b all
0 2604 877 3481
In [93]:
def z_test(gA, gB, alpha):
    for n in cnt_users_events_groups.index:
        alpha = 0.05  
        p1 = cnt_users_events_groups[gA][n] / df_ab[gA]
        p2 = cnt_users_events_groups[gB][n] / df_ab[gB]
        print(df_ab[gA], df_ab[gB])
        p_combined = (cnt_users_events_groups[gA][n] + cnt_users_events_groups[gB][n]) / (df_ab[gA] + df_ab[gB])     
        difference = p1 - p2
        z_value = difference / mth.sqrt(p_combined * (1 - p_combined) * (1/df_ab[gA] + 1/df_ab[gB]))
        distr = st.norm(0, 1)
        p_value = (1 - distr.cdf(abs(z_value))) * 2
        print('{} p-значение: {}'.format(cnt_users_events_groups['event_name'][n], p_value))
        if p_value < alpha: 
            print('Отвергаем нулевую гипотезу: между долями есть значимая разница')
        else:
            print('Не получилось отвергнуть нулевую гипотезу, нет оснований считать доли разными')
            print()

z_test('group_a', 'group_b', 0.05)
0    2604
Name: group_a, dtype: int64 0    877
Name: group_b, dtype: int64
login p-значение: [0.08481837]
Не получилось отвергнуть нулевую гипотезу, нет оснований считать доли разными

0    2604
Name: group_a, dtype: int64 0    877
Name: group_b, dtype: int64
product_cart p-значение: [0.21469192]
Не получилось отвергнуть нулевую гипотезу, нет оснований считать доли разными

0    2604
Name: group_a, dtype: int64 0    877
Name: group_b, dtype: int64
product_page p-значение: [6.94273936e-06]
Отвергаем нулевую гипотезу: между долями есть значимая разница
0    2604
Name: group_a, dtype: int64 0    877
Name: group_b, dtype: int64
purchase p-значение: [0.04652483]
Отвергаем нулевую гипотезу: между долями есть значимая разница
0    2604
Name: group_a, dtype: int64 0    877
Name: group_b, dtype: int64
All p-значение: [nan]
Не получилось отвергнуть нулевую гипотезу, нет оснований считать доли разными

По результатам z-теста между группой А и группой В нет значимой разницы при регистрации.
Между группой А и группой В нет значимой разницы при просмотре карточки товара.
Между группой А и группой В есть значимая разница при просмотре страницы товара.
Между группой А и группой В есть значимая разница при покупке.

Описать выводы по этапу исследовательского анализа данных и по проведённой оценке результатов A/B-тестирования. Сделать общее заключение о корректности проведения теста.

Основное количество пользователей совершали действия в первые дни.

В группе А на 1 пользователя приходится 7 событий, в группе В на одного пользователя 6 событий. Распределение среднего количества событий на пользователя в выборках отличается на 16%.

Основное количество пользователей группы А регистрировались с 14 по 21 декабря. Максимальное количество пользователей группы В регистрировались 7 декабря..Пользователи регистрировались неравномерно. Пользователей группы В зарегистрировалось почти в 3 раза меньше.

Основное количество событий совершались в первые дни по двум группам. События пользователями группы В совершались в 3,7 раза меньше, чем пользователями группы А

Средняя сумма, потраченная пользователями группы В, в 4 раза меньше потраченной средней суммы группы А.

Пользователи теста в основном пользуются Android. Пользователей группы В, использующих Android, в 2,8 раза меньше чем группы А; пользователей группы В, использующих Mac, в 3,5 раза меньше чем группы А; пользователей группы В, использующих PC, в 3,2 раза меньше чем группы А; пользователей группы В, использующих iPhone, в 2,8 раза меньше чем группы А. Одинаковое распределение пользователей использующих Android и iPhone.

На мой взгляд, проведение теста некорректно в связи с тем, что: 1) Отсутствуют данные событий с 31 декабря по 4 января; 2) Период проведения акции выпадает на предновогодние праздники; 3) После исключения пользователей, которые не совершали действий, мы потеряли 45,2% данных по пользователям. У нас осталось 54,8% данных.

Не засчитываем, потому что он проводился в неподходящее время. Параллельно проходил другой тест

In [94]:
data['user_id'].nunique()
Out[94]:
3481